#include "f_ops.h"

#include "debug.h"
#include "inspect.h"

/** 100500 попытка повторить затёртый успех
    найти Cons по её индексу,
       если index>=0 то индексация от начала кортежа и 0 соотв. первой непустой ячейке
       если index<0 то индексация от конца кортежа и -1 соота. последней непустой ячейке
**/
Cons *
tupleIndex(Tcl_Interp *interp,Tcl_Obj *obj,int index,Cons **savePrev)
{
    Cons *cons,*prev;   // искомая ячейка и непосредственная перед ней
    int pos;
    if (obj->typePtr==lazyType)
        lazyObjExec(obj,1);
    SHOULD(obj->typePtr==tupleType || Tcl_ConvertToType(interp,obj,tupleType)==TCL_OK,return NULL);
    prev=NULL;
    if (index>=0) {
        // индексация от начала
        pos=0;
        prev=NULL;
        for(cons=TUPLE(obj)->head;cons;prev=cons,cons=cons->next) {
            if (cons->obj!=NULL) {
                if (cons->obj->typePtr==lazyType) lazyObjExec(cons->obj,1);
                if (cons->obj->typePtr==tupleType) {
                    consExpand(cons,&TUPLE(obj)->tail);
                    continue;
                }
            }
            if (cons->obj==NULL) continue;
            if (pos==index) break;
            pos++;
        }
    } else {
        // индексация от конца
        Cons *lead;     // лидер, который бежит впереди в поисках конца кортежа
        Cons *lastLazy; // последний нераскрытый tuple или lazy между cons и lead
        
        index=-index;   // это чтобы с отрицательными не путаться
        lastLazy=NULL;
        cons=NULL;
        for(pos=1,lead=TUPLE(obj)->head;lead;lead=lead->next) {
            RESTART:
            if (lead->obj==NULL) continue;
            if (lead->obj!=NULL) {
                if (lead->obj->typePtr==lazyType || lead->obj->typePtr==tupleType) {
                    lastLazy=lead;
                }
            }
            // удалить пустые ячейки следуюущие за lead - чтобы не мешали
            while(lead->next!=NULL && lead->next->obj==NULL) {
                lead->next=consFree(lead->next);
                if (lead->next==NULL) TUPLE(obj)->tail=lead;
            }
            // движение cons
            if (cons==NULL) {
                // начальное движение cons на первую непустую ячейку
                for(prev=NULL,cons=TUPLE(obj)->head;cons!=NULL;prev=cons,cons=cons->next) {
                    if (cons->obj!=NULL) break;
                }
            } else if (pos<index) {
                // пока lead не отбежал на должное расстояние, cons не двигается
                pos++;
            } else {
                // иначе сдвигается на след. непустую ячейку
                do {
                    if (prev==lastLazy) {
                        lastLazy=NULL;
                    }
                    prev=cons;
                    cons=cons->next;
                } while(cons->obj==NULL);
            }
            // критерии успеха/неуспеха
            if (lead->next==NULL) {
                // lead дошёл-таки до конца кортежа
                if (pos==index && lastLazy==NULL) {
                    // успех - cons находится на нужной позиции и нераскрытых ячеек нет
                    break;
                }
                if (pos<index && lastLazy==NULL) {
                    // неудачка
                    cons=NULL;
                    prev=NULL;
                    break;
                }
                // между cons и концом кортежа есть нераскрытые ячейки - надо раскрыть
                if (lastLazy->obj->typePtr==lazyType) lazyObjExec(lastLazy->obj,1);
                if (lastLazy->obj->typePtr==tupleType) consExpand(lastLazy,&TUPLE(obj)->tail);
                lastLazy=NULL;
                // и надо повторять с ячейки следующей за prev (cons->obj мог обнулиться)
                if (prev==NULL) cons=TUPLE(obj)->head;
                else cons=prev->next;
                for(;cons->obj==NULL;prev=cons,cons=cons->next)
                    ;
                pos=0;
                lead=cons;
                goto RESTART; 
            }
        }
    }
    if (savePrev!=NULL) *savePrev=prev;
    return cons;
}
/** вернуть объект из кортежа по индексу */
Tcl_Obj *
tupleObjIndex(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    Cons *cons;
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    SHOULD(obj->typePtr==tupleType || Tcl_ConvertToType(interp,obj,tupleType)==TCL_OK,return NULL);
    cons=tupleIndex(interp,obj,count,NULL);
    if (cons==NULL) return tupleObjNew();
    return cons->obj;
}

/** "парные" операции над кортежами
    0 - head
    1 - rest
*/
static __inline Tcl_Obj *
tupleObjOp2(Tcl_Interp *interp,int opcode,Tcl_Obj *tupleObj,int count)
{
    SHOULD(tupleObj!=NULL);
    if (count==0) return tupleObj;
    if (tupleObj->typePtr==lazyType) {
        lazyObjExec(tupleObj,1);
    }
    SHOULD(tupleObj->typePtr==tupleType || Tcl_ConvertToType(interp,tupleObj,tupleType)==TCL_OK,return NULL);
    Tcl_Obj *result;
    Cons *cons,*prev;
    //cons=consIndex(TUPLE(tupleObj)->head,TUPLE(tupleObj)->tail,count,&prev);
    cons=tupleIndex(interp,tupleObj,count,&prev);
/*    if ((cons==NULL && prev!=NULL) || (cons!=NULL && prev==NULL))
        return tupleObj;
    if (cons==NULL && prev==NULL)
        return tupleObjNew();
*/        
//    TRACE("prev=%p prev->next=%p %s",prev,(prev?prev->next:NULL),(prev->obj?Tcl_GetString(prev->obj):""));
//    TRACE("cons=%p cons->next=%p %s",cons,(cons?cons->next:NULL),(cons->obj?Tcl_GetString(cons->obj):""));
    switch (opcode) {
        case 0: /* HEAD - ячейки от начала до prev */
            if (cons==NULL) {
                result=tupleObj;
                break;
            }
            result=tupleObjNew();
            if (Tcl_IsShared(tupleObj)) {
                // copy cons
                TUPLE(result)->head=consClone(TUPLE(tupleObj)->head,prev,&TUPLE(result)->tail);
            } else {
                // steal cons
                TUPLE(result)->head=TUPLE(tupleObj)->head;
                TUPLE(result)->tail=prev;
                prev->next=NULL;
                TUPLE(tupleObj)->head=cons;
                if (cons==NULL)
                    TUPLE(tupleObj)->tail=NULL;
                Tcl_InvalidateStringRep(tupleObj);
            }
        break;
        case 1: /* REST  - ячейки от cons до конца */
            if (cons==NULL) {
                result=tupleObj;
                break;
            }
            result=tupleObjNew();
            if (Tcl_IsShared(tupleObj)) {
                // copy
//                TRACE("copy");
                TUPLE(result)->head=consClone(cons,TUPLE(tupleObj)->tail,&TUPLE(result)->tail);
            } else {
                // steal
//                TRACE("steal");
                TUPLE(result)->head=cons;
                TUPLE(result)->tail=TUPLE(tupleObj)->tail;
                TUPLE(tupleObj)->tail=prev;
                if (prev==NULL)
                    TUPLE(tupleObj)->head=NULL;
                else
                    prev->next=NULL;
                Tcl_InvalidateStringRep(tupleObj);
            }
        break;
        case 2: /* FRONT,TAIL - unimplemented */
        case 3: break;
        case 4: /* ROTATE - exchange left and right */
            if (cons==NULL) {
                result=tupleObj;
                break;
            }
            result=tupleObjNew();
            if (Tcl_IsShared(tupleObj)) {
                Cons *tmp;
                TUPLE(result)->head=consClone(cons,TUPLE(tupleObj)->tail,&TUPLE(result)->tail);
                tmp=TUPLE(result)->tail;
                tmp->next=consClone(TUPLE(tupleObj)->head,prev,&TUPLE(result)->tail);
            } else {
                TUPLE(result)->head=cons;
                TUPLE(result)->tail=prev;
                TUPLE(tupleObj)->tail->next=TUPLE(tupleObj)->head;
                prev->next=NULL;
                TUPLE(tupleObj)->head=TUPLE(tupleObj)->tail=NULL;
                Tcl_InvalidateStringRep(tupleObj);
            }
            break;
        default:
            ERR("invalid opcode for operation");
            return NULL;
    }
    return result;
}

Tcl_Obj *
tupleObjHead(Tcl_Interp *interp,Tcl_Obj *tupleObj,int count)
{
    if (count==0) return tupleObjNew();
    return tupleObjOp2(interp,0,tupleObj,count);
}

Tcl_Obj *
tupleObjRest(Tcl_Interp *interp,Tcl_Obj *tupleObj,int count)
{
    if (count==0) return tupleObj;
    return tupleObjOp2(interp,1,tupleObj,count);
}

Tcl_Obj *
tupleObjFront(Tcl_Interp *interp,Tcl_Obj *tupleObj,int count)
{
    if (count==0) return tupleObj;
    return tupleObjOp2(interp,0,tupleObj,-count);
}

Tcl_Obj *
tupleObjTail(Tcl_Interp *interp,Tcl_Obj *tupleObj,int count)
{
    if (count==0) return tupleObjNew();
    return tupleObjOp2(interp,1,tupleObj,-count);
}

Tcl_Obj *
tupleObjRotate(Tcl_Interp *interp,Tcl_Obj *tupleObj,int count)
{
    if (count==0) return tupleObj;
    return tupleObjOp2(interp,4,tupleObj,-count);
}

Tcl_Obj *
listObjHead(Tcl_Interp *interp,Tcl_Obj *listObj,int count)
{
    SHOULD(listObj!=NULL);
    if (count==0) return listObj;
    SHOULD(listObj->typePtr==listType || Tcl_ConvertToType(interp,listObj,listType)==TCL_OK,return NULL);
    int objc;
    Tcl_Obj **objv;
    SHOULD(Tcl_ListObjGetElements(interp,listObj,&objc,&objv)==TCL_OK,return NULL);
    if (count>0) {
        if (count>objc) count=objc;
        return Tcl_NewListObj(count,objv);
    }
    count=-count;
    if (count>objc) count=objc;
    return Tcl_NewListObj(objc-count,objv);
}
Tcl_Obj *
listObjRest(Tcl_Interp *interp,Tcl_Obj *listObj,int count)
{
    SHOULD(listObj!=NULL);
    if (count==0) return listObj;
    SHOULD(listObj->typePtr==listType || Tcl_ConvertToType(interp,listObj,listType)==TCL_OK,return NULL);
    int objc;
    Tcl_Obj **objv;
    SHOULD(Tcl_ListObjGetElements(interp,listObj,&objc,&objv)==TCL_OK,return NULL);
    if (count>0) {
        if (count>objc) count=objc;
        return Tcl_NewListObj(objc-count,objv+count);
    }
    count=-count;
    if (count>objc) count=objc;
    
    return Tcl_NewListObj(count,objv+objc-count);
}

Tcl_Obj *
listObjFront(Tcl_Interp *interp,Tcl_Obj *listObj,int count)
{
    return listObjHead(interp,listObj,-count);
}

Tcl_Obj *
listObjTail(Tcl_Interp *interp,Tcl_Obj *listObj,int count)
{
    return listObjRest(interp,listObj,-count);
}
Tcl_Obj *
listObjRotate(Tcl_Interp *interp,Tcl_Obj *listObj,int count)
{
    Tcl_Obj *tmp;
    int objc;
    Tcl_Obj **objv;

    SHOULD(listObj!=NULL);

    if (count==0) return listObj;

    SHOULD(listObj->typePtr==listType || Tcl_ConvertToType(interp,listObj,listType)==TCL_OK,return NULL);    


    SHOULD(Tcl_ListObjGetElements(interp,listObj,&objc,&objv)==TCL_OK,return NULL);
    if (count>objc)
        return listObj;
    
    tmp=Tcl_NewListObj(count,objv);
    Tcl_IncrRefCount(tmp);
    
    if (Tcl_IsShared(listObj)) listObj=Tcl_NewListObj(objc-count,objv+count);
    else Tcl_ListObjReplace(interp,listObj,0,count,0,NULL);
    
    Tcl_ListObjAppendList(interp,listObj,tmp);
    Tcl_DecrRefCount(tmp);
    return listObj;
}
Tcl_Obj *
objHead(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) return tupleObjHead(interp,obj,count);
    return listObjHead(interp,obj,count);
}


Tcl_Obj *
objRest(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) return tupleObjRest(interp,obj,count);
    return listObjRest(interp,obj,count);
}

Tcl_Obj *
objFront(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) return tupleObjFront(interp,obj,count);
    return listObjFront(interp,obj,count);
}

Tcl_Obj *
objTail(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) return tupleObjTail(interp,obj,count);
    return listObjTail(interp,obj,count);
}

Tcl_Obj *
objRotate(Tcl_Interp *interp,Tcl_Obj *obj,int count)
{
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) return tupleObjRotate(interp,obj,count);
    return listObjRotate(interp,obj,count);
}

Tcl_Obj *
objLast(Tcl_Interp *interp,Tcl_Obj *obj)
{
    Tcl_Obj *result;
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) {
        result=tupleObjIndex(interp,obj,-1);
    } else {
        int objc;
        Tcl_Obj **objv;
        if (Tcl_ListObjGetElements(interp,obj,&objc,&objv)!=TCL_OK || objc==0) return NULL;
        result=objv[objc-1];
    }
    return result;
}

Tcl_Obj *
objFirst(Tcl_Interp *interp,Tcl_Obj *obj)
{
    Tcl_Obj *result;
    SHOULD(obj!=NULL,return NULL);
    if (obj->typePtr==lazyType) {
        lazyObjExec(obj,1);
    }
    if (obj->typePtr==tupleType) {
        result=tupleObjIndex(interp,obj,0);
    } else {
        int objc;
        Tcl_Obj **objv;
        if (Tcl_ListObjGetElements(interp,obj,&objc,&objv)!=TCL_OK || objc==0) return NULL;
        result=objv[0];
    }
    return result;
}

// tcl: head ?count? obj
int
headObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]) {
    int count;
    Tcl_Obj *result;
    Tcl_Obj *obj;
    (void)data;
    if (objc==2) {
        count=1;
        obj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetIntFromObj(interp,objv[1],&count)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        obj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? obj");
        return TCL_ERROR;
    }
    result=objHead(interp,obj,count);
    if (result==NULL)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}
// tcl: rest ?count? obj
int
restObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]) {
    int count;
    Tcl_Obj *result;
    Tcl_Obj *obj;
    (void)data;
    if (objc==2) {
        count=1;
        obj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetIntFromObj(interp,objv[1],&count)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        obj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? obj");
        return TCL_ERROR;
    }
    result=objRest(interp,obj,count);
    if (result==NULL)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

// tcl: front ?count? obj
int
frontObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]) {
    int count;
    Tcl_Obj *result;
    Tcl_Obj *obj;
    (void)data;
    if (objc==2) {
        count=1;
        obj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetIntFromObj(interp,objv[1],&count)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        obj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? obj");
        return TCL_ERROR;
    }
    result=objFront(interp,obj,count);
    if (result==NULL)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}
// tcl: tail ?count? obj
int
tailObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]) {
    int count;
    Tcl_Obj *result;
    Tcl_Obj *obj;
    (void)data;
    if (objc==2) {
        count=1;
        obj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetIntFromObj(interp,objv[1],&count)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        obj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? obj");
        return TCL_ERROR;
    }
    result=objTail(interp,obj,count);
    if (result==NULL)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

// tcl: tail ?count? obj
int
rotateObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]) {
    int count;
    Tcl_Obj *result;
    Tcl_Obj *obj;
    (void)data;
    if (objc==2) {
        count=1;
        obj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetIntFromObj(interp,objv[1],&count)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        obj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? obj");
        return TCL_ERROR;
    }
    result=objRotate(interp,obj,count);
    if (result==NULL)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

int
firstObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    (void)data;
    if (objc!=2) {
        Tcl_WrongNumArgs(interp,objc,objv,"obj");
        return TCL_ERROR;
    }
    result=objFirst(interp,objv[1]);
    if (result==NULL) return TCL_ERROR;
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}
int
lastObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    (void)data;
    if (objc!=2) {
        Tcl_WrongNumArgs(interp,objc,objv,"obj");
        return TCL_ERROR;
    }
    result=objLast(interp,objv[1]);
    if (result==NULL) result=Tcl_NewObj();
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

//Tcl_Obj *
//tupleObjHead(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
//{
//    return tupleObjHeadOrRest(interp,tupleObj,index,0);
//}
/*
Tcl_Obj *
tupleObjRest(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    return tupleObjHeadOrRest(interp,tupleObj,index,1);
}

Tcl_Obj *
tupleObjTail(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    return tupleObjHeadOrRest(interp,tupleObj,-index,1);
}

Tcl_Obj *
tupleObjFront(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    return tupleObjHeadOrRest(interp,tupleObj,-index,0);
}

Tcl_Obj *
tupleObjRotate(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    return NULL;//tupleObjHeadOrRest(interp,tupleObj,index,2);
}
*/
Tcl_Obj *
tupleObjTwin(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    (void)interp;
    (void)tupleObj;
    (void)index;
    return NULL;//tupleObjHeadOrRest(interp,tupleObj,index,3);
}

Tcl_Obj *
tupleObjRtwin(Tcl_Interp *interp,Tcl_Obj *tupleObj,int index)
{
    (void)interp;
    (void)tupleObj;
    (void)index;
    return NULL;//tupleObjHeadOrRest(interp,tupleObj,index,4);
}

static __inline int 
tupleOpCommand(Tcl_Obj *(*op)(Tcl_Interp *,Tcl_Obj *,int),Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    long index;
    Tcl_Obj *tupleObj;
    Tcl_Obj *result;
    if (objc==2) {
        index=1;
        tupleObj=objv[1];
    } else if (objc==3) {
        if (Tcl_GetLongFromObj(interp,objv[1],&index)!=TCL_OK) {
            Tcl_SetResult(interp,"\"count\" should be a integer",NULL);
            return TCL_ERROR;
        }
        tupleObj=objv[2];
    } else {
        Tcl_WrongNumArgs(interp,objc,objv,"?count? tupleObj");
        return TCL_ERROR;
    }
    result=op(interp,tupleObj,index);
    if (result==NULL) {
        Tcl_SetResult(interp,"inside tupleOpCommand" , NULL);
        return TCL_ERROR;
    }
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

/*int
headObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjHead,interp,objc,objv);
}

int
tailObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjTail,interp,objc,objv);
}

int
restObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjRest,interp,objc,objv);
}

int
frontObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjFront,interp,objc,objv);
}

int
rotateObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjRotate,interp,objc,objv);
}
*/
int
twinObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjTwin,interp,objc,objv);
}

int
rtwinObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    return tupleOpCommand(tupleObjRtwin,interp,objc,objv);
}

